﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using TV_Organizer.Properties;
using TV_Organizer.Subtitles;
using System.Windows.Data;
using io = System.IO;

namespace TV_Organizer
{
    public class File : DependencyObject
    {
        #region ctors + init

        public File(string fullPath)
        {
            this.Initialize();
            if (!string.IsNullOrEmpty(fullPath))
                this.SetPath(fullPath);
        }

        private File()
        {
            this.Initialize();
        }

        private void Initialize()
        {
            this.MatchingShows = new ObservableCollection<TVShow>();
            this.Subtitles = new ObservableCollection<SubtitleContainer>();
            this.SelectedSubtitles = new ObservableCollection<Subtitle>();
            this.Subtitles.CollectionChanged += OnSubtitles_CollectionChanged;
        }

        #endregion

        #region props

        #region Source File

        public static readonly DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        /// <summary>
        /// Full path
        /// </summary>
        public string Path
        {
            get { return this.SafelyGetValue<string>(PathProperty); }
            private set { this.SafelySetValue(PathProperty, value); }
        }

        public static readonly DependencyProperty FilenameProperty = DependencyProperty.Register("Filename", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        /// <summary>
        /// File name (without extension)
        /// </summary>
        public string Filename
        {
            get { return this.SafelyGetValue<string>(FilenameProperty); }
            private set { this.SafelySetValue(FilenameProperty, value); }
        }

        public static readonly DependencyProperty ExtensionProperty = DependencyProperty.Register("Extension", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        /// <summary>
        /// Extension (ex: .avi)
        /// </summary>
        public string Extension
        {
            get { return this.SafelyGetValue<string>(ExtensionProperty); }
            private set { this.SafelySetValue(ExtensionProperty, value); }
        }

        public static readonly DependencyProperty FolderProperty = DependencyProperty.Register("Folder", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        /// <summary>
        /// Fullpath to containing folder
        /// </summary>
        public string Folder
        {
            get { return this.SafelyGetValue<string>(FolderProperty); }
            private set { this.SafelySetValue(FolderProperty, value); }
        }

        #endregion

        public static readonly DependencyProperty ProcessFileProperty = DependencyProperty.Register("ProcessFile", typeof(bool), typeof(File), new UIPropertyMetadata(false));
        public bool ProcessFile
        {
            get { return this.SafelyGetValue<bool>(ProcessFileProperty); }
            set { this.SafelySetValue(ProcessFileProperty, value); }
        }

        #region Analyzed props

        public static readonly DependencyProperty SeasonProperty = DependencyProperty.Register("Season", typeof(int), typeof(File), new UIPropertyMetadata(-1));
        public int Season
        {
            get { return this.SafelyGetValue<int>(SeasonProperty); }
            set { this.SafelySetValue(SeasonProperty, value); }
        }

        public static readonly DependencyProperty EpisodeProperty = DependencyProperty.Register("Episode", typeof(int), typeof(File), new UIPropertyMetadata(-1));
        public int Episode
        {
            get { return this.SafelyGetValue<int>(EpisodeProperty); }
            set { this.SafelySetValue(EpisodeProperty, value); }
        }

        public static readonly DependencyProperty SelectedShowProperty = DependencyProperty.Register("SelectedShow", typeof(TVShow), typeof(File), new UIPropertyMetadata(null));
        public TVShow SelectedShow
        {
            get { return this.SafelyGetValue<TVShow>(SelectedShowProperty); }
            set { this.SafelySetValue(SelectedShowProperty, value); }
        }

        public static readonly DependencyProperty EpisodeTitleProperty = DependencyProperty.Register("EpisodeTitle", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        public string EpisodeTitle
        {
            get { return this.SafelyGetValue<string>(EpisodeTitleProperty); }
            set { this.SafelySetValue(EpisodeTitleProperty, value); }
        }

        public static readonly DependencyProperty TeamProperty = DependencyProperty.Register("Team", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        public string Team
        {
            get { return this.SafelyGetValue<string>(TeamProperty); }
            set { this.SafelySetValue(TeamProperty, value); }
        }

        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        public string Source
        {
            get { return this.SafelyGetValue<string>(SourceProperty); }
            set { this.SafelySetValue(SourceProperty, value); }
        }

        public static readonly DependencyProperty CodecProperty = DependencyProperty.Register("Codec", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        public string Codec
        {
            get { return this.SafelyGetValue<string>(CodecProperty); }
            set { this.SafelySetValue(CodecProperty, value); }
        }

        public static readonly DependencyProperty FormatProperty = DependencyProperty.Register("Format", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        public string Format
        {
            get { return this.SafelyGetValue<string>(FormatProperty); }
            set { this.SafelySetValue(FormatProperty, value); }
        }

        public static readonly DependencyProperty IsSampleProperty = DependencyProperty.Register("IsSample", typeof(bool), typeof(File), new UIPropertyMetadata(false));
        public bool IsSample
        {
            get { return this.SafelyGetValue<bool>(IsSampleProperty); }
            set { this.SafelySetValue(IsSampleProperty, value); }
        }

        public bool IsMediaFile
        {
            get { return Settings.Default.MediaExtensions.Contains(this.Extension); }
        }

        public bool IsAttachedFile
        {
            get { return Settings.Default.AttachedFilesExtensions.Contains(this.Extension); }
        }

        public static readonly DependencyProperty NeedsMoveProperty = DependencyProperty.Register("NeedsMove", typeof(bool?), typeof(File), new UIPropertyMetadata(null));
        public bool? NeedsMove
        {
            get { return this.SafelyGetValue<bool?>(NeedsMoveProperty); }
            set { this.SafelySetValue(NeedsMoveProperty, value); }
        }

        #endregion

        #region Target File

        public static readonly DependencyProperty TargetFilePathProperty = DependencyProperty.Register("TargetFilePath", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        public string TargetFilePath
        {
            get { return this.SafelyGetValue<string>(TargetFilePathProperty); }
            set { this.SafelySetValue(TargetFilePathProperty, value); }
        }

        public static readonly DependencyProperty TargetFolderProperty = DependencyProperty.Register("TargetFolder", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        public string TargetFolder
        {
            get { return this.SafelyGetValue<string>(TargetFolderProperty); }
            set { this.SafelySetValue(TargetFolderProperty, value); }
        }

        public static readonly DependencyProperty TargetFilenameProperty = DependencyProperty.Register("TargetFilename", typeof(string), typeof(File), new UIPropertyMetadata(string.Empty));
        public string TargetFilename
        {
            get { return this.SafelyGetValue<string>(TargetFilenameProperty); }
            set { this.SafelySetValue(TargetFilenameProperty, value); }
        }

        #endregion

        #region TAGS

        public static readonly DependencyProperty IsRealProperty = DependencyProperty.Register("IsReal", typeof(bool), typeof(File), new UIPropertyMetadata(false));
        public bool IsReal
        {
            get { return this.SafelyGetValue<bool>(IsRealProperty); }
            set { this.SafelySetValue(IsRealProperty, value); }
        }

        public static readonly DependencyProperty IsProperProperty = DependencyProperty.Register("IsProper", typeof(bool), typeof(File), new UIPropertyMetadata(false));
        public bool IsProper
        {
            get { return this.SafelyGetValue<bool>(IsProperProperty); }
            set { this.SafelySetValue(IsProperProperty, value); }
        }

        public static readonly DependencyProperty IsRepackProperty = DependencyProperty.Register("IsRepack", typeof(bool), typeof(File), new UIPropertyMetadata(false));
        public bool IsRepack
        {
            get { return this.SafelyGetValue<bool>(IsRepackProperty); }
            set { this.SafelySetValue(IsRepackProperty, value); }
        }

        public static readonly DependencyProperty IsReripProperty = DependencyProperty.Register("IsRerip", typeof(bool), typeof(File), new UIPropertyMetadata(false));
        public bool IsRerip
        {
            get { return this.SafelyGetValue<bool>(IsReripProperty); }
            set { this.SafelySetValue(IsReripProperty, value); }
        }

        public static readonly DependencyProperty IsInternalProperty = DependencyProperty.Register("IsInternal", typeof(bool), typeof(File), new UIPropertyMetadata(false));
        public bool IsInternal
        {
            get { return this.SafelyGetValue<bool>(IsInternalProperty); }
            set { this.SafelySetValue(IsInternalProperty, value); }
        }

        #endregion

        #region Observale Collections

        public ObservableCollection<TVShow> MatchingShows { get; private set; }

        public ObservableCollection<SubtitleContainer> Subtitles { get; private set; }

        public static readonly DependencyProperty SelectedSubtitlesProperty = DependencyProperty.Register("SelectedSubtitles", typeof(ObservableCollection<Subtitle>), typeof(File));
        public ObservableCollection<Subtitle> SelectedSubtitles
        {
            get { return (ObservableCollection<Subtitle>)GetValue(SelectedSubtitlesProperty); }
            set { SetValue(SelectedSubtitlesProperty, value); }
        }

        #endregion

        #endregion

        #region methods

        public void SetPath(string fullPath)
        {
            this.Path = fullPath;
            this.Filename = io.Path.GetFileNameWithoutExtension(this.Path);
            this.Extension = io.Path.GetExtension(this.Path);
            this.Folder = io.Path.GetDirectoryName(this.Path);
        }

        public override string ToString()
        {
            return this.Path;
        }

        public void Analyze()
        {
            // Season + Episode
            int episode, season;
            int seasonMatchIndex = GetSeasonAndEpisode(this.Filename, out season, out episode);
            this.Episode = episode;
            this.Season = season;

            // Source
            foreach (var regex in FileScanner.Constants.SourceRegexes)
            {
                if (regex.IsMatch(this.Filename))
                {
                    this.Source = regex.Name;
                    break;
                }
            }
            if (Settings.Default.ApplyHDTV && string.IsNullOrEmpty(this.Source))
                this.Source = "HDTV";

            // Format
            foreach (var regex in FileScanner.Constants.FormatRegexes)
            {
                if (regex.IsMatch(this.Filename))
                {
                    this.Format = regex.Name;
                    break;
                }
            }
            if (Settings.Default.Apply720pToMKV && string.IsNullOrEmpty(this.Codec) && this.Extension == ".mkv")
                this.Format = "720p";

            // Codec
            foreach (var regex in FileScanner.Constants.CodecRegexes)
            {
                if (regex.IsMatch(this.Filename))
                {
                    this.Codec = regex.Name;
                    break;
                }
            }
            if (Settings.Default.ApplyX264ToMKV && string.IsNullOrEmpty(this.Codec) && this.Extension == ".mkv")
                this.Codec = "x264";
            else if (Settings.Default.ApplyXviDToAVI && string.IsNullOrEmpty(this.Codec) && this.Extension == ".avi")
                this.Codec = "XviD";
            
            // Team
            foreach (var regex in FileScanner.Constants.TeamRegexes)
            {
                if (regex.IsMatch(this.Filename))
                {
                    this.Team = regex.Name;
                    break;
                }
            }

            // IsSample
            if (FileScanner.Constants.IsSampleRegex.IsMatch(this.Filename))
                this.IsSample = true;

            // IsReal
            if (FileScanner.Constants.IsRealRegex.IsMatch(this.Filename))
                this.IsReal = true;

            // IsProper
            if (FileScanner.Constants.IsProperRegex.IsMatch(this.Filename))
                this.IsProper = true;

            // IsRepack
            if (FileScanner.Constants.IsRepackRegex.IsMatch(this.Filename))
                this.IsRepack = true;

            // IsRerip
            if (FileScanner.Constants.IsReripRegex.IsMatch(this.Filename))
                this.IsRerip = true;

            // IsInternal
            if (FileScanner.Constants.IsInternalRegex.IsMatch(this.Filename))
                this.IsInternal = true;

            // Show name
            var connector = ConnectorsManager.Instance.GetTVShowsConnector(Settings.Default.SelectedTVShowConnector);
            if (connector != null && seasonMatchIndex > 0)
            {
                var beforeSeason = this.Filename.Substring(0, seasonMatchIndex - 1).ReplaceChars(FileScanner.Constants.CleanupCharacters, FileScanner.Constants.CleanupCharactersReplacement);

                var searchResults = connector.SearchShow(beforeSeason);
                if (searchResults.Count() > 0)
                {
                    Application.Current.Dispatcher.Invoke(new Action<IEnumerable<TVShow>>((shows) =>
                    {
                        this.MatchingShows.AddRange(shows);
                    }), new object[] { searchResults });
                }
            }

            var l_count = this.MatchingShows.Count;
            if (l_count > 0)
                this.SelectedShow = this.MatchingShows[0];
            if (l_count == 1)
                this.ProcessFile = true;
        }

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            this.UpdateTargetFile();
        }

        public void UpdateTargetFile()
        {
            string l_destinationFolder = this.Folder;
            if (l_destinationFolder == null) return;

            if (Settings.Default.MoveTvShows)
            {
                l_destinationFolder = System.IO.Path.Combine(Settings.Default.TVShowsFolder, FileScanner.FolderNameParser.GetFolderName(this));
            }

            string l_destinationFilename = this.Filename;
            if (Settings.Default.RenameFiles)
            {
                l_destinationFilename = string.Format("{0}.S{1}E{2}", this.SelectedShow != null ? this.SelectedShow.Name : string.Empty, this.Season.ToString("D2"), this.Episode.ToString("D2"));

                if (!string.IsNullOrEmpty(this.EpisodeTitle))
                    l_destinationFilename += string.Format(".{0}", this.EpisodeTitle);

                if (this.IsRerip)
                    l_destinationFilename += string.Format(".RERiP");

                if (this.IsInternal)
                    l_destinationFilename += string.Format(".iNTERNAL");

                if (this.IsReal)
                    l_destinationFilename += string.Format(".REAL");

                if (this.IsProper)
                    l_destinationFilename += string.Format(".PROPER");

                if (this.IsRepack)
                    l_destinationFilename += string.Format(".REPACK");

                if (this.Format != (string)File.FormatProperty.DefaultMetadata.DefaultValue)
                    l_destinationFilename += string.Format(".{0}", this.Format);

                if (this.Source != (string)File.SourceProperty.DefaultMetadata.DefaultValue)
                    l_destinationFilename += string.Format(".{0}", this.Source);

                if (this.Codec != (string)File.CodecProperty.DefaultMetadata.DefaultValue)
                    l_destinationFilename += string.Format(".{0}", this.Codec);

                if (this.Team != (string)File.TeamProperty.DefaultMetadata.DefaultValue)
                    l_destinationFilename += string.Format("-{0}", this.Team);

                if (this.IsSample)
                    l_destinationFilename += string.Format(" - sample");

                l_destinationFilename = l_destinationFilename.RemoveChars(System.IO.Path.GetInvalidFileNameChars());
            }

            var l_targetFileName = string.Format("{0}{1}", l_destinationFilename, this.Extension);

            this.TargetFolder = l_destinationFolder;
            this.TargetFilename = l_targetFileName;
            this.TargetFilePath = System.IO.Path.Combine(l_destinationFolder, l_targetFileName);

            if (this.SelectedShow == null)
                this.NeedsMove = null;
            else
                this.NeedsMove = !System.IO.Path.Equals(this.TargetFilePath.ToLower(), this.Path.ToLower());
        }

        public static File GetCommonFileForEdition(IEnumerable<File> files)
        {
            if (files.Count() == 1)
            {
                return files.First();
            }

            var l_result = new File();

            File.SetCommonValue(files, File.SeasonProperty, l_result);
            File.SetCommonValue(files, File.EpisodeProperty, l_result);
            File.SetCommonValue(files, File.CodecProperty, l_result);
            File.SetCommonValue(files, File.SourceProperty, l_result);
            File.SetCommonValue(files, File.TeamProperty, l_result);
            File.SetCommonValue(files, File.SelectedShowProperty, l_result);

            return l_result;
        }

        private static void SetCommonValue(IEnumerable<DependencyObject> sourceDPObjects, DependencyProperty dp, DependencyObject targetDPObject)
        {
            var l_values = sourceDPObjects.Select(dpo => dpo.SafelyGetValue<object>(dp)).Distinct();
            if (l_values.Count() == 1)
            {
                targetDPObject.SafelySetValue(dp, l_values.First());
            }
        }

        public void ApplyChangedPropertiesToFile(File file)
        {
            if (this.SelectedShow != null)
            {
                if (!file.MatchingShows.Contains(this.SelectedShow))
                    file.MatchingShows.Add(this.SelectedShow);
                file.SelectedShow = this.SelectedShow;
            }

            this.ApplyChangedPropertyToFile(file, File.SeasonProperty);
            this.ApplyChangedPropertyToFile(file, File.EpisodeProperty);
            this.ApplyChangedPropertyToFile(file, File.FormatProperty);
            this.ApplyChangedPropertyToFile(file, File.SourceProperty);
            this.ApplyChangedPropertyToFile(file, File.CodecProperty);
            this.ApplyChangedPropertyToFile(file, File.TeamProperty);
            //this.ApplyChagedPropertyToFile(file, File.IsRealProperty);
            //this.ApplyChagedPropertyToFile(file, File.IsProperProperty);
            //this.ApplyChagedPropertyToFile(file, File.IsRepackProperty);
            //this.ApplyChagedPropertyToFile(file, File.IsSampleProperty);
        }

        private void ApplyChangedPropertyToFile(File file, DependencyProperty dp)
        {
            var l_value = this.SafelyGetValue<object>(dp);
            if (l_value != dp.DefaultMetadata.DefaultValue)
                file.SafelySetValue(dp, l_value);
        }

        public Task FindAndAttachToMediaFileAync(IEnumerable<File> mediaFiles)
        {
            if (!this.IsAttachedFile)
                throw new InvalidOperationException("File.FindAndAttachToMediaFileAync: only works on attached files");

            return TaskEx.Run(() =>
            {
                var mediaFile = mediaFiles.OrderByDescending(mf => File.GetSimilarityBetween(this, mf)).FirstOrDefault();
                if (mediaFile != null)
                {
                    mediaFile.ApplyChangedPropertiesToFile(this);
                    mediaFile.UpdateTargetFile();
                }
            });
        }

        #endregion

        #region Subtitles handling

        public async Task AddSubtitlesAsync(IEnumerable<Subtitle> subtitles)
        {
            var tasks = new List<Task<SubtitleContainer>>();
            foreach (var subtitle in subtitles)
            {
                tasks.Add(SubtitleContainer.CreateAsync(subtitle, this));
            }
            await Task.Factory.StartNew(() => Task.WaitAll(tasks.ToArray()));

            var result = tasks.Select(t => t.Result);
            foreach (var sub in result)
                sub.Subtitle.IsSelectedChanged += new EventHandler<EventArgs>(Subtitle_IsSelectedChanged);

            this.Subtitles.AddRange(result);
        }

        private void Subtitle_IsSelectedChanged(object sender, EventArgs e)
        {
            var sub = sender as Subtitle;
            if (sub == null) return;

            if (sub.IsSelected)
                this.SelectedSubtitles.Add(sub);
            else
                this.SelectedSubtitles.Remove(sub);
        }

        private void OnSubtitles_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
            {
                this.SelectedSubtitles.Clear();
            }

            if (e.OldItems != null)
            {
                foreach(var subContainer in e.OldItems.OfType<SubtitleContainer>())
                {
                    if (this.SelectedSubtitles.Contains(subContainer.Subtitle))
                        this.SelectedSubtitles.Remove(subContainer.Subtitle);
                }
            }
        }

        #endregion

        #region Static methods

        /// <summary>
        /// Get season and episode for the given filename
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="season">Season number if found. -1 otherwise.</param>
        /// <param name="episode">Episode number if found. -1 otherwise.</param>
        /// <returns>Index of the first character of the match in the filename. -1 if there was no match.</returns>
        public static int GetSeasonAndEpisode(string filename, out int season, out int episode)
        {
            season = episode = -1;

            foreach (var regex in FileScanner.Constants.SesonEpisodeRegexes)
            {
                if (regex.IsMatch(filename))
                {
                    var match = regex.Match(filename);
                    if (match.Groups.Count == 3 && int.TryParse(match.Groups[1].Value, out season) && int.TryParse(match.Groups[2].Value, out episode))
                    {
                        return match.Index;
                    }
                }
            }

            return -1;
        }

        private static readonly DependencyProperty[] _propertiesToCompare = new DependencyProperty[]
        {
            File.CodecProperty,
            File.EpisodeProperty,
            File.FormatProperty,
            File.IsInternalProperty,
            File.IsProperProperty,
            File.IsRealProperty,
            File.IsRepackProperty,
            File.IsReripProperty,
            File.SeasonProperty,
            File.SelectedShowProperty,
            File.SourceProperty,
            File.TeamProperty,
        };

        /// <summary>
        /// Returns the similarity between to Files.
        /// </summary>
        /// <param name="file1"></param>
        /// <param name="file2"></param>
        /// <returns>A double between 1.0 (if the files are the same) and 0.0 (if the files have nothing in common).</returns>
        public static double GetSimilarityBetween(File file1, File file2)
        {
            int totalMatching = 0;
            foreach (var property in _propertiesToCompare)
                totalMatching += GetSimilarityBetween(file1, file2, property);

            return (double)totalMatching / (double)_propertiesToCompare.Length;
        }

        private static int GetSimilarityBetween(File file1, File file2, DependencyProperty property)
        {
            var value1 = file1.SafelyGetValue<object>(property);
            var value2 = file2.SafelyGetValue<object>(property);

            if (value1 == null)
            {
                return value2 == null ? 1 : 0;
            }

            return value1.Equals(value2) ? 1 : 0;
        }

        #endregion

        #region SubtitleContainer subclass

        public class SubtitleContainer
        {
            private SubtitleContainer(Subtitle subtitle, File owningFile)
            {
                this.Subtitle = subtitle;

                if (this.Subtitle == null)
                    throw new ArgumentNullException();
                if (string.IsNullOrEmpty(this.Subtitle.Filename))
                    throw new ArgumentException();

                var subtitleFile = new File(subtitle.Filename.RemoveChars(System.IO.Path.GetInvalidFileNameChars()));
                subtitleFile.Analyze();
                this.SimilarityPercentage = (int)Math.Round(GetSimilarityBetween(subtitleFile, owningFile) * 100);
            }

            public static async Task<SubtitleContainer> CreateAsync(Subtitle subtitle, File owningFile)
            {
                var task = TaskEx.Run<SubtitleContainer>(() =>
                {
                    return new SubtitleContainer(subtitle, owningFile);
                });
                await task;
                return task.Result;
            }

            public Subtitle Subtitle { get; private set; }

            public const string SimilarityPercentagePropertyName = "SimilarityPercentage";
            public int SimilarityPercentage { get; private set; }
        }

        #endregion
    }
}
